package com.seelyn.rhea.mvc.internal;

import com.seelyn.rhea.mvc.context.Request;
import com.seelyn.rhea.mvc.internal.enums.HttpMethod;
import com.seelyn.rhea.mvc.internal.enums.RheaWay;
import com.seelyn.rhea.mvc.plugin.Plugin;
import com.seelyn.rhea.mvc.plugin.PluginBuilder;
import com.seelyn.rhea.mvc.route.*;

import java.util.*;

/**
 * 框架核心类,为系统添加route,filter,resolver等
 * @author slacrey
 * @since 2016/1/25
 */
public final class RheaFactory implements BuilderFactory, PluginBuilder,
        RouteFactory {

    private final Map<String, List<RouteMatching>> routesMap = new HashMap<>();
    private final Map<String, List<ResolverMatching>> resolverMap = new HashMap<>();
    private final Map<String, List<FilterMatching>> filtersMap = new HashMap<>();

    private final Map<String, List<String>> pathRoutes  = new HashMap<>();
    private final Map<String, List<String>> pathResolvers  = new HashMap<>();

    private final List<Plugin> plugins = new ArrayList<>();

    public static RheaFactory getInstance() {
        return RheaFactoryHolder.factory;
    }

    private RheaFactory() {

    }

    private static class RheaFactoryHolder {
        private static final RheaFactory factory = new RheaFactory();
    }

    public BuilderFactory builder() {
        return getInstance();
    }

    private boolean checkPathRepeat(String name, Map<String, List<String>> pathMap,
                                    String methodName, String path) throws PathRepeatException {

        List<String> paths = pathMap.get(methodName);
        if (paths == null || paths.isEmpty()) {
            paths = new ArrayList<>();
            paths.add(path);
            pathMap.put(methodName, paths);
        } else {
            if (paths.contains(path)) {
                throw new PathRepeatException("There are multiple path corresponding to " + name + ":" + path);
            } else {
                paths.add(path);
                pathMap.put(methodName, paths);
            }
        }
        return false;

    }

    private RouteMatching getRoute(Request request) {
        List<RouteMatching> routes = routesMap.get(request.requestMethod());
        if (routes == null || routes.isEmpty()) {
            return null;
        }
        RouteMatching route = null;
        for (RouteMatching tmp : routes) {
            if (tmp.matching(request.uri())) {
                route = tmp;
                break;
            }
        }
        return route;
    }

    private void filterSort(List<FilterMatching> filters) {
        Collections.sort(filters, new Comparator<FilterMatching>() {
            public int compare(FilterMatching arg0, FilterMatching arg1) {
                return arg0.order() - arg1.order();
            }
        });
    }

    private List<FilterMatching> getBeforeFilter(Request request) {
        List<FilterMatching> resultFilters = new ArrayList<>();
        List<FilterMatching> filters = filtersMap.get(request.beforeFilter());
        if (filters == null || filters.isEmpty()) {
            return null;
        }
        for (FilterMatching tmp : filters) {
            if (tmp.matching(request.uri())) {
                resultFilters.add(tmp);
            }
        }
        filterSort(resultFilters);
        return resultFilters;
    }

    private List<FilterMatching> getAfterFilter(Request request) {
        List<FilterMatching> resultFilters = new ArrayList<>();
        List<FilterMatching> filters = filtersMap.get(request.afterFilter());
        if (filters == null || filters.isEmpty()) {
            return null;
        }
        for (FilterMatching tmp : filters) {
            if (tmp.matching(request.uri())) {
                resultFilters.add(tmp);
            }
        }
        filterSort(resultFilters);
        return resultFilters;
    }

    public void addRoute(String requestMethod, InternalRoute route) throws PathRepeatException {

        if (!checkPathRepeat(RheaWay.route.name(), pathRoutes, requestMethod, route.getPath())) {
            if (routesMap.containsKey(requestMethod)) {

                routesMap.get(requestMethod).add(route);
            } else {
                List<RouteMatching> routes = new ArrayList<>(1);
                routes.add(route);
                routesMap.put(requestMethod, routes);
            }
        }

    }

    public void addFilter(String requestMethod, InternalFilter filter) {
        if (filtersMap.containsKey(requestMethod)) {
            filtersMap.get(requestMethod).add(filter);
        } else {
            List<FilterMatching> filters = new ArrayList<>(1);
            filters.add(filter);
            filtersMap.put(requestMethod, filters);
        }
    }

    public void addResolver(String requestMethod, InternalResolver resolver) throws PathRepeatException {

        if (!checkPathRepeat(RheaWay.resolver.name(), pathResolvers, requestMethod, resolver.getPath())) {
            if (resolverMap.containsKey(requestMethod)) {

                resolverMap.get(requestMethod).add(resolver);
            } else {
                List<ResolverMatching> routes = new ArrayList<>(1);
                routes.add(resolver);
                resolverMap.put(requestMethod, routes);
            }
        }

    }


    @Override
    public BuilderRoute route(Route route) {
        return new BuilderRouteImpl(route);
    }

    @Override
    public BuilderFilter filter(Filter filter) {
        return new BuilderFilterImpl(filter);
    }

    @Override
    public BuilderResolver resolver(Resolver resolver) {
        return new BuilderResolverImpl(resolver);
    }

    public Query query() {
        return new QueryImpl();
    }

    public PluginBuilder plugin() {
        return getInstance();
    }

    @Override
    public void addPlugin(Plugin plugin) {
        plugins.add(plugin);
    }

    @Override
    public List<Plugin> getPlugins() {
        return Collections.unmodifiableList(plugins);
    }

    static class QueryImpl implements Query {

        @Override
        public RouteMatching getRoute(Request request) {
            return getInstance().getRoute(request);
        }

        @Override
        public List<FilterMatching> getBeforeFilters(Request request) {
            return getInstance().getBeforeFilter(request);
        }

        @Override
        public List<FilterMatching> getAfterFilters(Request request) {
            return getInstance().getAfterFilter(request);
        }

    }

    static class BuilderRouteImpl implements BuilderRoute {

        private Route route;
        public BuilderRouteImpl(Route route) {
            this.route = route;
        }


        @Override
        public BuilderRoute get(String path) throws PathRepeatException {

            getInstance().addRoute(HttpMethod.GET.name(), InternalRoute.create(path, route));

            return this;
        }

        @Override
        public BuilderRoute head(String path) throws PathRepeatException {
            getInstance().addRoute(HttpMethod.HEAD.name(), InternalRoute.create(path, route));
            return this;
        }

        @Override
        public BuilderRoute post(String path) throws PathRepeatException {
            getInstance().addRoute(HttpMethod.POST.name(), InternalRoute.create(path, route));
            return this;
        }

        @Override
        public BuilderRoute put(String path) throws PathRepeatException {
            getInstance().addRoute(HttpMethod.PUT.name(), InternalRoute.create(path, route));
            return this;
        }

        @Override
        public BuilderRoute patch(String path) throws PathRepeatException {
            getInstance().addRoute(HttpMethod.PATCH.name(), InternalRoute.create(path, route));
            return this;
        }

        @Override
        public BuilderRoute delete(String path) throws PathRepeatException {
            getInstance().addRoute(HttpMethod.DELETE.name(), InternalRoute.create(path, route));
            return this;
        }

        @Override
        public BuilderRoute options(String path) throws PathRepeatException {
            getInstance().addRoute(HttpMethod.OPTIONS.name(), InternalRoute.create(path, route));
            return this;
        }

        @Override
        public BuilderRoute trace(String path) throws PathRepeatException {
            getInstance().addRoute(HttpMethod.TRACE.name(), InternalRoute.create(path, route));
            return this;
        }

    }

    static class BuilderFilterImpl implements BuilderFilter {

        private Filter filter;

        public BuilderFilterImpl(Filter filter) {
            this.filter = filter;
        }

        @Override
        public BuilderFilter before(String path) {
            getInstance().addFilter(HttpMethod.BEFORE.name(), InternalFilter.create(path, filter));
            return this;
        }

        @Override
        public BuilderFilter after(String path) {
            getInstance().addFilter(HttpMethod.AFTER.name(), InternalFilter.create(path, filter));
            return this;
        }
    }

    static class BuilderResolverImpl implements BuilderResolver {

        private Resolver resolver;

        public BuilderResolverImpl(Resolver resolver) {
            this.resolver = resolver;
        }

        @Override
        public BuilderResolver all(String path) throws PathRepeatException {
            for (HttpMethod httpMethod: HttpMethod.values()) {
                getInstance().addResolver(httpMethod.name(), InternalResolver.create(path, resolver));
            }
            return this;
        }

        @Override
        public BuilderResolver get(String path) throws PathRepeatException {
            getInstance().addResolver(HttpMethod.GET.name(), InternalResolver.create(path, resolver));
            return this;
        }

        @Override
        public BuilderResolver head(String path) throws PathRepeatException {
            getInstance().addResolver(HttpMethod.HEAD.name(), InternalResolver.create(path, resolver));
            return this;
        }

        @Override
        public BuilderResolver post(String path) throws PathRepeatException {
            getInstance().addResolver(HttpMethod.POST.name(), InternalResolver.create(path, resolver));
            return this;
        }

        @Override
        public BuilderResolver put(String path) throws PathRepeatException {
            getInstance().addResolver(HttpMethod.PUT.name(), InternalResolver.create(path, resolver));
            return this;
        }

        @Override
        public BuilderResolver patch(String path) throws PathRepeatException {
            getInstance().addResolver(HttpMethod.PATCH.name(), InternalResolver.create(path, resolver));
            return this;
        }

        @Override
        public BuilderResolver delete(String path) throws PathRepeatException {
            getInstance().addResolver(HttpMethod.DELETE.name(), InternalResolver.create(path, resolver));
            return this;
        }

        @Override
        public BuilderResolver options(String path) throws PathRepeatException {
            getInstance().addResolver(HttpMethod.OPTIONS.name(), InternalResolver.create(path, resolver));
            return this;
        }

        @Override
        public BuilderResolver trace(String path) throws PathRepeatException {
            getInstance().addResolver(HttpMethod.TRACE.name(), InternalResolver.create(path, resolver));
            return this;
        }

    }

    public void startup() {
        startPlugins();
    }

    public void shutdown() {
        routesMap.clear();
        filtersMap.clear();
        resolverMap.clear();

        pathRoutes.clear();
        pathResolvers.clear();
        stopPlugins();
    }

    private void stopPlugins() {
        for (Plugin plugin : plugins) {
            plugin.shutdown();
        }
    }

    private void startPlugins() {
        for (Plugin plugin : plugins) {
            plugin.startup();
        }
    }
}
